package net.simpleframework.swing;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import net.simpleframework.core.ALoggerAware;
import net.simpleframework.core.Logger;
import net.simpleframework.core.ado.db.EOrder;
import net.simpleframework.swing.JTableExDialogs.ProgressWindow;
import net.simpleframework.util.ConvertUtils;
import net.simpleframework.util.LangUtils;
import net.simpleframework.util.NumberUtils;
import net.simpleframework.util.StringUtils;

import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.renderer.AbstractRenderer;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.IconValue;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.sort.SortController;
import org.jdesktop.swingx.sort.TableSortController;
import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
import org.jdesktop.swingx.table.TableColumnExt;

import sun.swing.table.DefaultTableCellHeaderRenderer;

/**
 * 这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。
 * 
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
@SuppressWarnings("serial")
public abstract class JAbstractTableEx extends JXTable {

	protected final Logger logger = ALoggerAware.getLogger(getClass());

	static Color headerColor = Color.decode("#6A1303"), headerSelectedColor = Color
			.decode("#EB8113");

	public JAbstractTableEx() {
	}

	private int maxHeaderHeight = 0;

	private void initTableHeader() {
		final JTableHeader tableHeader = getTableHeader();
		tableHeader.setForeground(headerColor);
		tableHeader.setBackground(UIManager.getColor("Button.background"));
		tableHeader.setDefaultRenderer(new DefaultTableCellHeaderRenderer() {
			private static final long serialVersionUID = -7783066742477039521L;

			@Override
			public Component getTableCellRendererComponent(final JTable table, final Object value,
					final boolean isSelected, final boolean hasFocus, final int row, final int col) {
				final Component renderer = super.getTableCellRendererComponent(table, value,
						isSelected, hasFocus, row, col);
				final int height = super.getPreferredSize().height;
				if (height > (dataTbl != null ? dataTbl.maxHeaderHeight : maxHeaderHeight)) {
					if (dataTbl != null) {
						dataTbl.maxHeaderHeight = height;
					} else {
						maxHeaderHeight = height;
					}
				}
				initHeaderRendererComponent((JComponent) renderer, row, col);
				return renderer;
			}

			@Override
			public Dimension getPreferredSize() {
				return new Dimension(0, Math.max(maxHeaderHeight, 27));
			}
		});

		tableHeader.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(final MouseEvent e) {
				if (tableHeader.getCursor().getType() != 0) {
					return;
				}
				clearAllSelection(getOppositeTable());
				final int col = tableHeader.columnAtPoint(e.getPoint());
				if (col < 0) {
					return;
				}

				if (SwingUtilities.isLeftMouseButton(e)) {
					if (e.getClickCount() == 2) {
						final SortOrder sortOrder = getSortOrder(col);
						if (sortOrder == SortOrder.UNSORTED) {
							setSortOrder(col, SortOrder.ASCENDING);
						} else if (sortOrder == SortOrder.ASCENDING) {
							setSortOrder(col, SortOrder.DESCENDING);
						} else {
							setSortOrder(col, SortOrder.UNSORTED);
						}
					} else {
						if (e.isControlDown()) {
							getColumnModel().getSelectionModel().addSelectionInterval(col, col);
						} else if (e.isShiftDown()) {
							int col2 = getSelectedColumn();
							if (col2 == -1) {
								col2 = col;
							}
							getColumnModel().getSelectionModel().setSelectionInterval(col, col2);
						} else {
							getColumnModel().getSelectionModel().setSelectionInterval(col, col);
						}

						final SortController<?> controller = getSortController();
						if (controller != null) {
							controller.setSortOrderCycle();
						}
						runningMark = true;
						getSelectionModel().addSelectionInterval(0, getRowCount() - 1);
						runningMark = false;
					}
				}
			}
		});
	}

	protected void initHeaderRendererComponent(final JComponent rendererComponent, final int row,
			final int col) {
		if (col == -1) {
			return;
		}
		final JTableExColumn column = getPrivateTableExColumn(col);
		rendererComponent.setToolTipText(column != null ? StringUtils.text(column.getTooltip(),
				column.getColumnText()) : null);
		if (LangUtils.contains(getSelectedColumns(), col)) {
			rendererComponent.setForeground(headerSelectedColor);
		}
	}

	protected void initTable() {
		createFixViewport();

		setColumnControlVisible(true);
		setHorizontalScrollEnabled(true);
		setEditable(false);
		setGridColor(Color.LIGHT_GRAY);
		setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED);
		setRowSorter(new TableSortControllerEx(getModel()));
		if (isFixTable()) {
			setHorizontalScrollEnabled(false);
			setColumnControlVisible(false);
		} else {
			final JScrollPane sp = getScrollPane();
			if (sp != null) {
				sp.setRowHeaderView(fixViewport);
				sp.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, fixTbl.getTableHeader());
			}
		}

		initTableHeader();
	}

	/***************************** public *****************************/

	public void addRow(final Object[] rowData) {
		((DefaultTableModel) getModel()).addRow(rowData);
	}

	public void addRow(final Vector<Object> rowData) {
		((DefaultTableModel) getModel()).addRow(rowData);
	}

	@Override
	public Object getValueAt(final int row, final int column) {
		try {
			return super.getValueAt(row, column);
		} catch (final Exception e) {
		}
		return null;
	}

	public Object getValueAt(final int row, final String columnName) {
		return getValueAt(row, getTableExColumn(columnName));
	}

	public Object getValueAt(final int row, final JTableExColumn column) {
		if (row < 0) {
			return null;
		}
		final TableColumn tc;
		if (column != null && ((tc = column.getTableColumn()) != null)) {
			try {
				if (!isFixTable() && column.isFreezable()) {
					return fixTbl.getValueAt(row, column);
				} else {
					return getModel().getValueAt(convertRowIndexToModel(row), tc.getModelIndex());
				}
			} catch (final Exception e) {
			}
		}
		return null;
	}

	public void setValueAt(final int row, final JTableExColumn column, final Object value) {
		if (row < 0) {
			return;
		}
		final TableColumn tc;
		if (column != null && ((tc = column.getTableColumn()) != null)) {
			try {
				final TableModel tm = (!isFixTable() && column.isFreezable()) ? fixTbl.getModel()
						: getModel();
				tm.setValueAt(value, convertRowIndexToModel(row), tc.getModelIndex());
			} catch (final Exception e) {
			}
		}
	}

	public void setValueAt(final int row, final String columnName, final Object value) {
		setValueAt(row, getTableExColumn(columnName), value);
	}

	@Override
	public void setRowHeight(final int rowHeight) {
		super.setRowHeight(rowHeight);
		if (fixTbl != null) {
			fixTbl.setRowHeight(rowHeight);
		}
	}

	@Override
	public void setRowHeight(final int row, final int rowHeight) {
		super.setRowHeight(row, rowHeight);
		if (fixTbl != null) {
			fixTbl.setRowHeight(row, rowHeight);
		}
	}

	@Override
	public synchronized void addMouseListener(final MouseListener l) {
		super.addMouseListener(l);
		if (fixTbl != null) {
			fixTbl.addMouseListener(l);
		}
	}

	@Override
	public synchronized void addKeyListener(final KeyListener l) {
		super.addKeyListener(l);
		if (fixTbl != null) {
			fixTbl.addKeyListener(l);
		}
	}

	@Override
	public void setSelectionMode(final int selectionMode) {
		super.setSelectionMode(selectionMode);
		if (fixTbl != null) {
			fixTbl.setSelectionMode(selectionMode);
		}
	}

	@Override
	public int[] getSelectedRows() {
		final int[] rows = super.getSelectedRows();
		if (rows == null || rows.length == 0) {
			if (fixTbl != null) {
				return fixTbl.getSelectedRows();
			}
		}
		return rows;
	}

	@Override
	public int getSelectedRow() {
		final int row = super.getSelectedRow();
		if (row < 0) {
			if (fixTbl != null) {
				return fixTbl.getSelectedRow();
			}
		}
		return row;
	}

	@Override
	public int getSelectedRowCount() {
		final int rowCount = super.getSelectedRowCount();
		if (rowCount == 0) {
			if (fixTbl != null) {
				return fixTbl.getSelectedRowCount();
			}
		}
		return rowCount;
	}

	/***************************** override *****************************/

	protected boolean isShowLineNo() {
		return true;
	}

	/***************************** table columns *****************************/

	static final ArrayList<JTableExColumn> NULL_COLUMNS = new ArrayList<JTableExColumn>();

	protected Map<String, JTableExColumn> tableExColumns;

	protected Map<String, JTableExColumn> tableExAllColumns;

	public JTableExColumn getTableExColumn(final String columnName) {
		return (columnName != null && tableExAllColumns != null) ? tableExAllColumns.get(columnName)
				: null;
	}

	JTableExColumn getPrivateTableExColumn(final int viewIndex) {
		if (viewIndex < 0) {
			return null;
		}
		try {
			final Object object = getColumn(viewIndex).getIdentifier();
			if (object instanceof JTableExColumn) {
				return (JTableExColumn) object;
			}
		} catch (final Exception e) {
		}
		return null;
	}

	@Override
	public int getSelectedColumnCount() {
		return getSelectedTableExColumns().length;
	}

	public JTableExColumn[] getSelectedTableExColumns() {
		final ArrayList<JTableExColumn> al = new ArrayList<JTableExColumn>();
		if (fixTbl != null) {
			for (final int column : fixTbl.getSelectedColumns()) {
				final JTableExColumn tableExColumn = fixTbl.getPrivateTableExColumn(column);
				if (tableExColumn != null) {
					al.add(tableExColumn);
				}
			}
		}
		for (final int column : getSelectedColumns()) {
			final JTableExColumn tableExColumn = getPrivateTableExColumn(column);
			if (tableExColumn != null) {
				al.add(tableExColumn);
			}
		}
		return al.toArray(new JTableExColumn[al.size()]);
	}

	public JTableExColumn getSelectedTableExColumn() {
		final int selectedColumn = getSelectedColumn();
		if (selectedColumn < 0) {
			if (fixTbl != null) {
				return fixTbl.getPrivateTableExColumn(fixTbl.getSelectedColumn());
			}
		}
		return getPrivateTableExColumn(selectedColumn);
	}

	public List<JTableExColumn> getTableExAllColumns() {
		if (isFixTable()) {
			return dataTbl.getTableExAllColumns();
		}
		return tableExAllColumns != null ? new ArrayList<JTableExColumn>(tableExAllColumns.values())
				: NULL_COLUMNS;
	}

	List<JTableExColumn> getTableExColumns() {
		return tableExColumns != null ? new ArrayList<JTableExColumn>(tableExColumns.values())
				: NULL_COLUMNS;
	}

	public void setColumns(final Object[] columns) {
		if (columns == null) {
			return;
		}
		final JTableExColumn[] columnsEx = new JTableExColumn[columns.length];
		int i = 0;
		for (final Object column : columns) {
			if (column instanceof JTableExColumn) {
				columnsEx[i++] = (JTableExColumn) column;
			} else {
				columnsEx[i++] = new JTableExColumn(ConvertUtils.toString(column));
			}
		}
		setColumns(columnsEx);
	}

	private boolean initTableOK;

	public void setColumns(final JTableExColumn[] columns) {
		if (!initTableOK) {
			initTable();
			initTableOK = true;
		}
		if (columns == null) {
			return;
		}
		final Vector<String> columnIdentifiers = new Vector<String>();
		tableExColumns = new LinkedHashMap<String, JTableExColumn>();
		if (isFixTable()) {
			for (final JTableExColumn column : columns) {
				tableExColumns.put(column.getColumnName(), column);
			}
			if (dataTbl.isShowLineNo()) {
				columnIdentifiers.add(linenoColumnName);
			}
		} else {
			tableExAllColumns = new LinkedHashMap<String, JTableExColumn>();
			int i = 0;
			for (final JTableExColumn column : columns) {
				column.setOriginalIndex(i++);
				tableExAllColumns.put(column.getColumnName(), column);
			}
			final Map<String, JTableExColumn> freezableTableExColumns = new LinkedHashMap<String, JTableExColumn>();
			for (final JTableExColumn column : columns) {
				if (column.isFreezable()) {
					freezableTableExColumns.put(column.getColumnName(), column);
				} else {
					tableExColumns.put(column.getColumnName(), column);
				}
			}
			if (freezableTableExColumns.size() == 0) {
				setFreezableWidth(0);
			}
			fixTbl.setColumns(freezableTableExColumns.values().toArray(
					new JTableExColumn[freezableTableExColumns.size()]));
		}

		for (final JTableExColumn column : tableExColumns.values()) {
			columnIdentifiers.add(column.getColumnText());
		}
		((DefaultTableModel) getModel()).setColumnIdentifiers(columnIdentifiers);

		int j = 0;
		if (isFixTable() && dataTbl.isShowLineNo()) {
			final TableColumnExt tc = (TableColumnExt) columnModel.getColumn(0);
			tc.setResizable(false);
			tc.setSortable(false);
			tc.setPreferredWidth(22);
			tc.setMinWidth(22);
			tc.setMaxWidth(22);
			tc.setHeaderRenderer(new TableCellRenderer() {
				@Override
				public Component getTableCellRendererComponent(final JTable table, final Object value,
						final boolean isSelected, final boolean hasFocus, final int row, final int column) {
					return JTableExUtils.lineNoHeaderRenderer;
				}
			});
			j++;
		}

		final TableColumnModel columnModel = getColumnModel();
		for (final JTableExColumn column : tableExColumns.values()) {
			column.table = this;
			column.setModelIndex(-1);
			final TableColumnExt tc = (TableColumnExt) columnModel.getColumn(j++);
			tc.setIdentifier(column);

			final int width = column.getColumnWidth();
			if (width > 0) {
				tc.setWidth(width);
				tc.setPreferredWidth(width);
			}
			if (!isFixTable()) {
				continue;
			}
			tc.addPropertyChangeListener(new PropertyChangeListener() {
				@Override
				public void propertyChange(final PropertyChangeEvent evt) {
					if (!"width".equals(evt.getPropertyName())) {
						return;
					}

					final TableColumn resizingColumn = getTableHeader().getResizingColumn();
					if (resizingColumn == null) {
						return;
					}
					final List<TableColumn> columns = getColumns();
					final JTableExColumn lastTableExColumn = (JTableExColumn) columns.get(
							columns.size() - 1).getIdentifier();
					if (!resizingColumn.getIdentifier().equals(lastTableExColumn)) {
						return;
					}

					final int delta = ConvertUtils.toInt(evt.getNewValue())
							- ConvertUtils.toInt(evt.getOldValue());
					if (delta != 0) {
						final JViewport vp = dataTbl.fixViewport;
						final Dimension d = vp.getPreferredSize();
						final int freezableWidth = d.width + delta;
						vp.setPreferredSize(new Dimension(freezableWidth, d.height));
						setFreezableWidth(freezableWidth);
					}
				}
			});
		}
		for (final JTableExColumn column : tableExColumns.values()) {
			if (!column.isVisible()) {
				(column.getTableColumn()).setVisible(false);
			}
		}
	}

	protected String convertToString(final JTableExColumn column, final Object value) {
		if (value instanceof Date) {
			return ConvertUtils.toDateString((Date) value,
					StringUtils.text(column.getFormat(), ConvertUtils.defaultDatePattern));
		} else if (value instanceof Number) {
			return NumberUtils.format(column.getFormat(), (Number) value);
		} else {
			return ConvertUtils.toString(value);
		}
	}

	protected Object convertToObject(final JTableExColumn column, final String value) {
		final Class<?> c = column.getBeanPropertyType();
		if (Date.class.isAssignableFrom(c)) {
			return ConvertUtils.toDate(value,
					StringUtils.text(column.getFormat(), ConvertUtils.defaultDatePattern));
		} else {
			try {
				if (c != null) {
					return ConvertUtils.convert(value, c);
				}
			} catch (final Exception e) {
			}
		}
		return value;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	Vector backupDataVector(final boolean resetSort) {
		if (isFixTable()) {
			return dataTbl.backupDataVector(resetSort);
		}

		if (resetSort) {
			resetSortOrder();
		}

		final Vector dataVector = new Vector();
		final DefaultTableModel tm = (DefaultTableModel) getModel();
		final DefaultTableModel tm2 = (DefaultTableModel) fixTbl.getModel();
		final List<JTableExColumn> columns = getTableExAllColumns();
		final int count = tm.getRowCount();
		for (int i = 0; i < count; i++) {
			final Vector row = new Vector();
			dataVector.add(row);
			for (final JTableExColumn col : columns) {
				if (col.isFreezable()) {
					row.add(tm2.getValueAt(i, col.getModelIndex()));
				} else {
					row.add(tm.getValueAt(i, col.getModelIndex()));
				}
			}
		}
		return dataVector;
	}

	private int freezableWidth;

	public int getFreezableWidth() {
		return freezableWidth > 0 ? freezableWidth : (isFixTable() ? this : fixTbl)
				.getPreferredSize().width;
	}

	public void setFreezableWidth(final int freezableWidth) {
		this.freezableWidth = freezableWidth;
		if (fixTbl != null) {
			fixTbl.setFreezableWidth(freezableWidth);
		}
	}

	@Override
	public Dimension getPreferredSize() {
		final Dimension d = super.getPreferredSize();
		if (isFixTable()) {
			if (freezableWidth > 0) {
				d.width = freezableWidth;
			}
		}
		return d;
	}

	public void setColumnFreezable(final JTableExColumn[] column) {
		if (isFixTable()) {
			dataTbl.setColumnFreezable(column, false);
			return;
		}
		setColumnFreezable(column, true);
	}

	@SuppressWarnings({ "rawtypes" })
	protected void setColumnFreezable(final JTableExColumn[] freezableColumns,
			final boolean freezable) {
		if (freezableColumns == null) {
			return;
		}
		boolean m = false;
		for (final JTableExColumn freezableColumn : freezableColumns) {
			if (freezableColumn.isFreezable() != freezable) {
				m = true;
				break;
			}
		}
		if (m) {
			final Vector dataVector = backupDataVector(true);
			for (final JTableExColumn freezableColumn : freezableColumns) {
				freezableColumn.setFreezable(freezable);
			}

			resetColumnModel();
			final List<JTableExColumn> columns = getTableExAllColumns();
			setColumns(columns.toArray(new JTableExColumn[columns.size()]));

			refreshTableData(dataVector);
		}
	}

	@SuppressWarnings({ "rawtypes" })
	void refreshTableData(final Vector dataVector) {
		final DefaultTableModel tableModel = (DefaultTableModel) getModel();
		tableModel.setRowCount(0);

		for (final Object data : dataVector) {
			final Vector<?> rowData = (Vector<?>) data;
			tableModel.addRow(rowData);
		}
	}

	public void refreshTableData() {
		if (isFixTable()) {
			dataTbl.refreshTableData();
			return;
		}
		refreshTableData(backupDataVector(true));
	}

	/***************************** Column Model *****************************/

	public void resetColumnModel() {
		setColumnModel(createDefaultColumnModel());
		if (fixTbl != null) {
			fixTbl.resetColumnModel();
		}
	}

	@Override
	public int getRowMargin() {
		return isFixTable() ? 0 : super.getRowMargin();
	}

	abstract class DefaultTableColumnModelEx extends DefaultTableColumnModelExt {

		@Override
		public int getColumnMargin() {
			return isFixTable() ? 0 : super.getColumnMargin();
		}
	}

	@Override
	protected TableColumnModel createDefaultColumnModel() {
		final TableColumnModel tcm = new DefaultTableColumnModelEx() {
			private static final long serialVersionUID = 2842566424324546838L;

			@Override
			public void moveColumn(final int columnIndex, final int newIndex) {
				if (isFixTable()
						&& dataTbl.isShowLineNo()
						&& (getPrivateTableExColumn(columnIndex) == null || getPrivateTableExColumn(newIndex) == null)) {
					getTableHeader().repaint();
					return;
				}
				super.moveColumn(columnIndex, newIndex);
			}
		};
		tcm.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(final ListSelectionEvent e) {
				if (!e.getValueIsAdjusting()) {
					getTableHeader().repaint();
				}
			}
		});
		return tcm;
	}

	/***************************** TableModel *****************************/

	@Override
	protected TableModel createDefaultDataModel() {
		return new DefaultTableModelEx() {
			private static final long serialVersionUID = -5660983089433051923L;

			@SuppressWarnings({ "unchecked", "rawtypes" })
			@Override
			public void addRow(final Vector rowData) {
				if (isFixTable()) {
					final boolean lineNo = dataTbl.isShowLineNo();
					if (lineNo) {
						rowData.insertElementAt(0, 0);
					}
					super.addRow(rowData);
					if (lineNo) {
						updateLineNo(false);
					}
				} else {
					final Vector fixVector = new Vector();
					final Vector vector = new Vector();
					for (final JTableExColumn column : getTableExAllColumns()) {
						if (column.isFreezable()) {
							fixVector.add(rowData.get(column.getOriginalIndex()));
						} else {
							vector.add(rowData.get(column.getOriginalIndex()));
						}
					}
					if (fixTbl != null) {
						((DefaultTableModel) fixTbl.getModel()).addRow(fixVector);
					}
					super.addRow(vector);
					updateFixTable();
				}
				setColumnBeanPropertyType();
			}

			@Override
			public void setNumRows(final int rowCount) {
				super.setNumRows(rowCount);
				if (fixTbl != null) {
					((DefaultTableModel) fixTbl.getModel()).setNumRows(rowCount);
				}
			}
		};
	}

	public void clearData() {
		resetSortOrder();
		((DefaultTableModel) getModel()).setRowCount(0);
	}

	void setColumnBeanPropertyType() {
		if (getRowCount() != 1) {
			return;
		}
		for (final JTableExColumn column : getTableExColumns()) {
			final Object o = getValueAt(0, column);
			if (o != null) {
				final Class<?> propertyType = column.getBeanPropertyType();
				if (propertyType == null) {
					column.setBeanPropertyType(o.getClass());
				}
			}
		}
	}

	void updateFixTable() {
		if (isFixTable()) {
			dataTbl.updateFixTable();
			return;
		}
		if (fixViewport != null) {
			final Dimension d1 = fixTbl.getPreferredSize();
			final Dimension d2 = fixViewport.getPreferredSize();
			if (!d1.equals(d2)) {
				fixViewport.setPreferredSize(d1);
			}
		}
	}

	abstract class DefaultTableModelEx extends DefaultTableModel {
		@Override
		public Class<?> getColumnClass(final int columnIndex) {
			Class<?> columnClazz = null;
			final int viewColumnIndex = convertColumnIndexToView(columnIndex);
			if (viewColumnIndex > -1) {
				final JTableExColumn column = getPrivateTableExColumn(viewColumnIndex);
				if (column != null) {
					columnClazz = column.getBeanPropertyType();
				}
			}
			return columnClazz != null ? columnClazz : Object.class;
		}

		@Override
		public Object getValueAt(final int rowIndex, final int columnIndex) {
			return getObjectSafely(super.getValueAt(rowIndex, columnIndex));
		}
	}

	Object getObjectSafely(Object object) {
		if (object instanceof Number) {
			if (Double.isNaN(((Number) object).doubleValue())) {
				object = 0d;
			} else if (Double.isInfinite(((Number) object).doubleValue())) {
				object = "∞";
			}
		}
		return object;
	}

	/***************************** ListSelectionModel *****************************/
	protected boolean runningMark = false;

	@Override
	public boolean getColumnSelectionAllowed() {
		return true;// !isFixTable();
	}

	@Override
	protected ListSelectionModel createDefaultSelectionModel() {
		return new DefaultListSelectionModel() {
			private static final long serialVersionUID = 2842566424324546838L;

			void doSelectionInterval(final int index0, final int index1, final boolean add) {
				if (add) {
					super.addSelectionInterval(index0, index1);
				} else {
					super.setSelectionInterval(index0, index1);
				}

				if (runningMark) {
					return;
				}

				final JAbstractTableEx table = getOppositeTable();
				if (isFixTable()) {
					final Rectangle r = table.getVisibleRect();
					final int lastRow = (r.y + r.height) / table.getRowHeight();
					final int leadSelectionIndex = getSelectionModel().getLeadSelectionIndex();
					if (leadSelectionIndex > lastRow) {
						table.scrollRowToVisible(leadSelectionIndex);
					}

					if (getSelectedColumn() == 0) {
						setColumnSelectionInterval(0, getColumnCount() - 1);
						if (table.getColumnCount() > 0) {
							table.setColumnSelectionInterval(0, table.getColumnCount() - 1);
						}
						table.runningMark = true;
						if (add) {
							table.getSelectionModel().addSelectionInterval(index0, index1);
						} else {
							table.getSelectionModel().setSelectionInterval(index0, index1);
						}
						table.runningMark = false;
					} else {
						clearAllSelection(table);
					}
				} else {
					clearAllSelection(table);
				}
			}

			@Override
			public void setSelectionInterval(final int index0, final int index1) {
				doSelectionInterval(index0, index1, false);
			}

			@Override
			public void addSelectionInterval(final int index0, final int index1) {
				doSelectionInterval(index0, index1, true);
			}
		};
	}

	void clearAllSelection(final JAbstractTableEx table) {
		table.getSelectionModel().clearSelection();
		table.getColumnModel().getSelectionModel().clearSelection();
	}

	public void setSelectionRows(final int[] rows, final boolean allColumns) {
	}

	/***************************** Table Sort *****************************/

	@Override
	public void resetSortOrder() {
		resetSortOrderPrivate();
		if (fixTbl != null) {
			fixTbl.resetSortOrder();
		}
	}

	void resetSortOrderPrivate() {
		super.resetSortOrder();
		for (final JTableExColumn column : getTableExColumns()) {
			column.setOrder(EOrder.normal);
		}
		if (oppositeDataVector != null) {
			oppositeDataVector.clear();
			oppositeDataVector = null;
		}
	}

	private Vector<Object> oppositeDataVector;

	class TableSortControllerEx extends TableSortController<TableModel> {
		TableSortControllerEx(final TableModel tableModel) {
			setModel(tableModel);
			setSortsOnUpdates(false);
		}

		@SuppressWarnings({ "unchecked", "rawtypes" })
		@Override
		public Comparator<?> getComparator(final int column) {
			final Comparator c = super.getComparator(column);
			return new Comparator() {
				@Override
				public int compare(final Object o1, final Object o2) {
					try {
						return c.compare(o1, o2);
					} catch (final Exception e) {
						return StringUtils.blank(o1).compareTo(StringUtils.blank(o2));
					}
				}
			};
		}

		protected void setSortKeysPrivate(final List<? extends SortKey> sortKeys) {
			super.setSortKeys(sortKeys);
			if (!runningMark) {
				final JAbstractTableEx table = getOppositeTable();
				table.runningMark = true;
				table.resetSortOrderPrivate();
				table.runningMark = false;
			}
		}

		@Override
		public void setSortKeys(final List<? extends SortKey> sortKeys) {
			if (sortKeys != null && sortKeys.size() > 0) {
				final SortKey sortKey = sortKeys.get(0);
				final JTableExColumn tableExColumn = getPrivateTableExColumn(convertColumnIndexToView(sortKey
						.getColumn()));
				if (tableExColumn == null) {
					return;
				}
				final SortOrder order = sortKey.getSortOrder();
				if (order.ordinal() == tableExColumn.getOrder().ordinal()) {
					return;
				}
				setColumnOrder(tableExColumn, EOrder.values()[order.ordinal()]);
			}
			setSortKeysPrivate(sortKeys);
			syncOppositeTableModel(sortKeys);
		}

		protected void setColumnOrder(final JTableExColumn tableExColumn, final EOrder order) {
			for (final JTableExColumn column : getTableExColumns()) {
				column.setOrder(EOrder.normal);
			}
			tableExColumn.setOrder(order);
		}

		@SuppressWarnings({ "unchecked", "rawtypes" })
		protected void syncOppositeTableModel(final List<? extends SortKey> sortKeys) {
			final JAbstractTableEx table = getOppositeTable();
			if (getTableExColumns().size() == 0 || table.getTableExColumns().size() == 0) {
				return;
			}

			final DefaultTableModel tm = (DefaultTableModel) table.getModel();
			if (oppositeDataVector == null) {
				oppositeDataVector = new Vector();
				for (int i = 0; i < tm.getRowCount(); i++) {
					oppositeDataVector.add(new Vector((Vector) tm.getDataVector().get(i)));
				}
			}
			for (int i = 0; i < oppositeDataVector.size(); i++) {
				final Vector data = (Vector) oppositeDataVector.get(i);
				final int row = convertRowIndexToView(i);
				final Vector viewData = (Vector) tm.getDataVector().get(row);
				viewData.clear();
				viewData.addAll(data);
			}
			// final int[] rows = table.getSelectedRows();
			tm.fireTableDataChanged();
			// if (rows != null && rows.length > 0) {
			// for (final int row : rows) {
			// table.getSelectionModel().addSelectionInterval(row, row);
			// }
			// }

			final boolean unsorted = sortKeys == null || sortKeys.size() == 0
					|| sortKeys.get(0).getSortOrder() == SortOrder.UNSORTED;
			if (unsorted && oppositeDataVector != null) {
				oppositeDataVector.clear();
				oppositeDataVector = null;
			}
		}
	}

	JAbstractTableEx getOppositeTable() {
		return isFixTable() ? dataTbl : fixTbl;
	}

	/***************************** ScrollPane *****************************/

	@Override
	protected void configureEnclosingScrollPane() {
		super.configureEnclosingScrollPane();

		final JScrollPane sp = getScrollPane();
		if (sp.getRowHeader() == null && fixViewport != null) {
			sp.setRowHeaderView(fixViewport);
			sp.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, fixTbl.getTableHeader());
		}

		for (final JTableExColumn column : getTableExColumns()) {
			final TableColumn tc = column.getTableColumn();
			final int width = column.getColumnWidth();
			if (width > 0) {
				tc.setPreferredWidth(width);
			} else {
				final JLabel lbl = new JLabel(column.getColumnText());
				lbl.updateUI();
				final JLabel lbl2 = new JLabel(convertToString(column, getValueAt(0, column)));
				lbl2.updateUI();
				tc.setPreferredWidth(Math.max(lbl.getPreferredSize().width,
						lbl2.getPreferredSize().width) + 24);
			}
		}
	}

	private JScrollPane getScrollPane() {
		Container p = this;
		while (!(p instanceof JScrollPane)) {
			p = p.getParent();
			if (p == null) {
				return null;
			}
		}
		return (JScrollPane) p;
	}

	protected JScrollBar getScrollBar() {
		final JScrollPane p = getScrollPane();
		return p != null ? p.getVerticalScrollBar() : null;
	}

	/***************************** CellRenderer *****************************/

	@Override
	public TableCellRenderer getCellRenderer(final int row, final int col) {
		if (isFixTable()) {
			if (dataTbl.isShowLineNo() && col == 0) {
				return JTableExUtils.lineNoCellRenderer;
			}
		}

		TableCellRenderer renderer = null;
		CellStyle cellStyle = null;
		final JTableExColumn column = getPrivateTableExColumn(col);
		if (column != null) {
			final Object object = getValueAt(row, column);
			cellStyle = getCellStyle(row, column);
			if (cellStyle != null) {
				if (cellStyle.renderComponent == ECellRenderComponent.percent) {
					renderer = JTableExUtils.percentRenderer;
				} else if (cellStyle.renderComponent == ECellRenderComponent.multipleLine) {
					renderer = JTableExUtils.multipleLineRenderer;
				}
			}

			if (renderer == null) {
				StringValue stringValue = null;
				if (object == null || object instanceof String) {
					stringValue = JTableExUtils.defaultStringValue;
				} else if (object instanceof Number || object instanceof Date) {
					stringValue = getStringValue(column);
				}
				if (stringValue != null) {
					final CellStyle cs = cellStyle;
					renderer = new DefaultTableRenderer(stringValue,
							getIconValue(cellStyle != null ? cellStyle.icon : null), column.getAlignment()) {
						@Override
						public Component getTableCellRendererComponent(final JTable table,
								final Object value, final boolean isSelected, final boolean hasFocus,
								final int row, final int column) {
							final JComponent component = (JComponent) super.getTableCellRendererComponent(
									table, value, isSelected, hasFocus, row, column);
							final JAbstractTableEx tbl = (JAbstractTableEx) table;
							if (tbl.isFixTable()) {
								if (!hasFocus) {
									component.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
								}
								if (!isSelected) {
									if (cs == null || cs.backgroundColor == null) {
										component.setBackground(JTableExUtils.lineNoHeaderRenderer
												.getBackground());
									}
								}
							}
							return component;
						}
					};
				}
			}
		}

		if (renderer == null) {
			renderer = super.getCellRenderer(row, col);
		}
		if (renderer instanceof AbstractRenderer) {
			final AbstractRenderer r = (AbstractRenderer) renderer;
			r.setBackground(cellStyle != null ? cellStyle.backgroundColor : null);
			r.setForeground(cellStyle != null ? cellStyle.foregroundColor : null);
		}
		return renderer;
	}

	private final Map<Object, Object> oValueMap = new HashMap<Object, Object>();

	private StringValue getStringValue(final JTableExColumn column) {
		if (column == null) {
			return null;
		}
		final String columnName = column.getColumnName();
		StringValue stringValue = (StringValue) oValueMap.get(columnName);
		if (stringValue == null) {
			oValueMap.put(columnName, stringValue = new StringValue() {
				private static final long serialVersionUID = -6682796733955379757L;

				@Override
				public String getString(final Object value) {
					return convertToString(column, value);
				}
			});
		}
		return stringValue;
	}

	private IconValue getIconValue(final Icon icon) {
		if (icon == null) {
			return null;
		}
		IconValue iconValue = (IconValue) oValueMap.get(icon);
		if (iconValue == null) {
			oValueMap.put(icon, iconValue = new IconValue() {
				private static final long serialVersionUID = -360208604922648911L;

				@Override
				public Icon getIcon(final Object value) {
					return icon;
				}
			});
		}
		return iconValue;
	}

	protected CellStyle getCellStyle(final int row, final JTableExColumn column) {
		if (isFixTable()) {
			return dataTbl.getCellStyle(row, column);
		} else {
			return null;
		}
	}

	public class CellStyle {
		public ECellRenderComponent renderComponent;

		public Icon icon;

		public Color backgroundColor;

		public Color foregroundColor;
	}

	protected static enum ECellRenderComponent {
		percent, multipleLine
	}

	/***************************** fixTbl *****************************/

	protected JAbstractTableEx fixTbl, dataTbl;

	protected JViewport fixViewport;

	protected abstract JAbstractTableEx createFixTable();

	protected boolean isFixTable() {
		return dataTbl != null;
	}

	protected void createFixViewport() {
		if (!isFixTable()) {
			fixViewport = new JViewport();
			fixTbl = createFixTable();
			fixTbl.dataTbl = this;
			fixViewport.add(fixTbl);
		}
	}

	private static final String linenoColumnName = "LineNo";

	void updateLineNo(final boolean immediately) {
		if (!isFixTable()) {
			fixTbl.updateLineNo(immediately);
			return;
		}
		if (!dataTbl.isShowLineNo()) {
			return;
		}
		int count = getModel().getRowCount();
		if (immediately) {
			count = (int) Math.pow(10, String.valueOf(count).length() - 1);
		}
		int width = 0;
		if (count == 100) {
			width = 26;
		} else if (count == 1000) {
			width = 32;
		} else if (count == 10000) {
			width = 38;
		} else if (count == 100000) {
			width = 44;
		}

		if (width > 0) {
			final TableColumnModel columnModel = getColumnModel();
			if (columnModel.getColumnCount() <= 0) {
				return;
			}
			final TableColumnExt tc = (TableColumnExt) columnModel.getColumn(0);
			tc.setPreferredWidth(width);
			tc.setMaxWidth(width);
			tc.setMinWidth(width);
		}
	}

	protected ProgressWindow getProgressWindow() {
		if (fixTbl == null) {
			return null;
		}
		return new ProgressWindow(isFixTable() ? this : fixTbl);
	}

	static {
		UIManager.put("Table.ascendingSortIcon", SwingUtils.loadIcon("tbl-up.png"));
		UIManager.put("Table.descendingSortIcon", SwingUtils.loadIcon("tbl-down.png"));
	}
}
